package org.finesys.ai.core;


import static org.finesys.ai.core.LogLevel.DEBUG;

import java.net.InetSocketAddress;
import java.net.Proxy;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import org.finesys.ai.core.balance.BalanceResponse;
import org.finesys.ai.core.chat.ChatCompletionRequest;
import org.finesys.ai.core.chat.ChatCompletionResponse;
import org.finesys.ai.core.chat.ChatRequest;
import org.finesys.ai.core.completion.CompletionRequest;
import org.finesys.ai.core.completion.CompletionResponse;
import org.finesys.ai.core.embedding.EmbeddingRequest;
import org.finesys.ai.core.embedding.EmbeddingResponse;
import org.finesys.ai.core.image.GenerateImagesRequest;
import org.finesys.ai.core.image.GenerateImagesResponse;
import org.finesys.ai.core.models.ModelsResponse;
import org.finesys.ai.core.moderation.ModerationRequest;
import org.finesys.ai.core.moderation.ModerationResponse;
import org.finesys.ai.core.moderation.ModerationResult;
import org.finesys.ai.core.spi.OpenAiClientBuilderFactory;
import org.finesys.ai.core.spi.ServiceHelper;

import okhttp3.Dispatcher;
import reactor.core.publisher.Flux;

public abstract class OpenAiClient {


    /**
     * 获取账户余额
     *
     * @return BalanceResponse 对象，包含账户余额信息
     * @throws UnsupportedOperationException 如果此方法在当前环境中不支持
     */
    public BalanceResponse balance() {
        throw new UnsupportedOperationException();
    }

    /**
     * 根据提供的请求，同步或异步或流式返回任务完成的响应。
     *
     * @param request 请求对象，包含了完成任务的必要信息。
     * @return 返回任务完成的响应，根据方法实现，可以是同步、异步或流式响应。
     */
    public SyncOrAsyncOrStreaming<CompletionResponse> completion(CompletionRequest request) {
        return completion(new OpenAiClientContext(), request);
    }

    /**
     * 根据请求返回补全结果
     *
     * @param clientContext OpenAiClientContext 对象，用于存储客户端的上下文信息
     * @param request       CompletionRequest 对象，表示补全请求
     * @return SyncOrAsyncOrStreaming<CompletionResponse> 对象，表示补全响应的同步、异步或流式处理结果
     * @throws UnsupportedOperationException 当此方法不被支持时抛出此异常
     */
    public SyncOrAsyncOrStreaming<CompletionResponse> completion(
            OpenAiClientContext clientContext, CompletionRequest request) {
        throw new UnsupportedOperationException();
    }

    /**
     * 根据提示文本完成补全操作。
     *
     * @param prompt 提示文本
     * @return 返回 SyncOrAsyncOrStreaming<String> 类型的结果，其中包含补全后的文本。
     */
    public SyncOrAsyncOrStreaming<String> completion(String prompt) {
        return completion(new OpenAiClientContext(), prompt);
    }

    /**
     * 异步或同步或流式完成操作
     *
     * @param clientContext OpenAI客户端上下文对象
     * @param prompt        提示文本
     * @return SyncOrAsyncOrStreaming<String> 异步、同步或流式完成结果
     * @throws UnsupportedOperationException 如果方法未实现，抛出此异常
     */
    public SyncOrAsyncOrStreaming<String> completion(OpenAiClientContext clientContext,
                                                     String prompt) {
        throw new UnsupportedOperationException();
    }

    /**
     * 根据给定的请求参数，异步或同步地获取聊天补全结果
     *
     * @param request 聊天补全请求参数
     * @return 聊天补全响应结果，可以是同步、异步或流式响应
     */
    public SyncOrAsyncOrStreaming<ChatCompletionResponse> chatCompletion(
            ChatCompletionRequest request) {
        return chatCompletion(new OpenAiClientContext(), request);
    }

    /**
     * 根据客户端上下文和请求参数，获取聊天完成响应。
     *
     * @param clientContext 客户端上下文，包含调用聊天完成接口所需的所有信息。
     * @param request       聊天完成请求，包含需要生成的文本、上下文等参数。
     * @return 返回一个包含聊天完成响应的 {@link SyncOrAsyncOrStreaming} 对象。
     * @throws UnsupportedOperationException 当方法不被支持时抛出此异常。
     */
    public SyncOrAsyncOrStreaming<ChatCompletionResponse> chatCompletion(
            OpenAiClientContext clientContext,
            ChatCompletionRequest request) {
        throw new UnsupportedOperationException();
    }

    /**
     * 根据用户消息生成聊天补全结果。
     *
     * @param userMessage 用户发送的消息
     * @return 返回一个包含聊天补全结果的SyncOrAsyncOrStreaming<String>对象
     */
    public SyncOrAsyncOrStreaming<String> chatCompletion(String userMessage) {
        return chatCompletion(new OpenAiClientContext(), userMessage);
    }

    /**
     * 与用户进行对话并完成聊天任务。
     *
     * @param clientContext 客户端上下文信息，包含用户ID、历史对话记录等信息。
     * @param userMessage   用户发送的消息。
     * @return 返回类型为SyncOrAsyncOrStreaming<String>，表示聊天完成的结果，可以是同步、异步或流式返回。
     * @throws UnsupportedOperationException 抛出异常，表示当前不支持此操作。
     */
    public SyncOrAsyncOrStreaming<String> chatCompletion(
            OpenAiClientContext clientContext,
            String userMessage) {
        throw new UnsupportedOperationException();
    }

    /**
     * 根据用户消息生成聊天补全结果。
     *
     * @param request 用户发送的消息
     * @return 返回一个包含聊天补全结果的SyncOrAsyncOrStreaming<String>对象
     */
    public SyncOrAsyncOrStreaming<ChatCompletionResponse> chatCompletion(ChatRequest request) {
        ChatCompletionRequest.Builder builder = ChatCompletionRequest.builder();
        if (Objects.nonNull(request.getSystemMessage())) {
            builder.addSystemMessage(request.getSystemMessage());
        }
        if (Objects.nonNull(request.getUserMessage())) {
            builder.addUserMessage(request.getUserMessage());
        }
        if (Objects.nonNull(request.getAssistantMessage())) {
            builder.addAssistantMessage(request.getAssistantMessage());
        }
        if (Objects.nonNull(request.getModel())) {
            builder.model(request.getModel());
        }
        if (Objects.nonNull(request.getTemperature())) {
            builder.temperature(request.getTemperature());
        }
        if (Objects.nonNull(request.getMaxTokens())) {
            builder.maxCompletionTokens(request.getMaxTokens());
        }
        if (Objects.nonNull(request.getStopWords())) {
            builder.stop(request.getStopWords());
        }
        builder.stream(request.getStream());
        return chatCompletion(new OpenAiClientContext(), builder.build());
    }

    /**
     * 使用Flux响应流返回聊天补全的结果。
     *
     * @param request 聊天补全请求对象
     * @return 包含聊天补全响应的Flux流
     * @throws UnsupportedOperationException 抛出异常，表示当前方法不支持操作
     */
    public Flux<ChatCompletionResponse> chatFluxCompletion(ChatCompletionRequest request) {
        throw new UnsupportedOperationException();
    }

    /**
     * 使用Flux响应流返回聊天补全的结果。
     *
     * @param request 聊天补全请求对象
     * @return 包含聊天补全响应的Flux流
     * @throws UnsupportedOperationException 抛出异常，表示当前方法不支持操作
     */
    public Flux<ChatCompletionResponse> chatFluxCompletion(ChatRequest request) {
        throw new UnsupportedOperationException();
    }

    /**
     * 根据用户消息生成聊天回复流
     *
     * @param userMessage 用户发送的消息
     * @return 返回包含聊天回复的响应流
     * @throws UnsupportedOperationException 如果此方法不被支持，则抛出此异常
     */
    public Flux<ChatCompletionResponse> chatFluxCompletion(String userMessage) {
        throw new UnsupportedOperationException();
    }


    /**
     * 根据给定的EmbeddingRequest请求，异步或同步地获取EmbeddingResponse。
     *
     * @param request 请求参数，包含需要嵌入的文本和嵌入模型的参数等。
     * @return 一个SyncOrAsync对象，包含嵌入结果，可以根据需求选择同步或异步获取。
     */
    public SyncOrAsync<EmbeddingResponse> embedding(EmbeddingRequest request) {
        return embedding(new OpenAiClientContext(), request);
    }

    /**
     * 使用OpenAI客户端上下文和嵌入请求获取嵌入结果。
     *
     * @param clientContext OpenAI客户端上下文，包含调用OpenAI服务所需的信息。
     * @param request       嵌入请求，包含需要嵌入的文本或其他相关数据。
     * @return 嵌入结果，可能同步或异步返回。
     * @throws UnsupportedOperationException 如果当前环境不支持此方法，则抛出此异常。
     */
    public SyncOrAsync<EmbeddingResponse> embedding(OpenAiClientContext clientContext,
                                                    EmbeddingRequest request) {
        throw new UnsupportedOperationException();
    }

    /**
     * 使用默认上下文将输入字符串转换为嵌入向量列表。
     *
     * @param input 输入字符串
     * @return 包含嵌入向量列表的 SyncOrAsync 对象
     */
    public SyncOrAsync<List<Double>> embedding(String input) {
        return embedding(new OpenAiClientContext(), input);
    }

    /**
     * 使用OpenAI客户端上下文和输入文本生成嵌入向量
     *
     * @param clientContext OpenAI客户端上下文，包含生成嵌入向量所需的所有信息
     * @param input         需要生成嵌入向量的输入文本
     * @return 一个包含嵌入向量的列表，封装在SyncOrAsync类中
     * @throws UnsupportedOperationException 如果方法不被支持时抛出此异常
     */
    public SyncOrAsync<List<Double>> embedding(OpenAiClientContext clientContext,
                                              String input) {
        throw new UnsupportedOperationException();
    }

    /**
     * 根据请求对内容进行审核
     *
     * @param request 审核请求参数
     * @return 审核结果，可能是同步返回，也可能是异步返回
     */
    public SyncOrAsync<ModerationResponse> moderation(ModerationRequest request) {
        return moderation(new OpenAiClientContext(), request);
    }

    /**
     * 对内容进行审核
     *
     * @param clientContext OpenAiClient上下文信息
     * @param request       审核请求
     * @return 审核结果，可以是同步或异步返回
     * @throws UnsupportedOperationException 抛出异常，表示当前操作不支持
     */
    public SyncOrAsync<ModerationResponse> moderation(OpenAiClientContext clientContext,
                                                      ModerationRequest request) {
        throw new UnsupportedOperationException();
    }

    /**
     * 对输入内容进行审核
     *
     * @param input 待审核的内容
     * @return 返回审核结果，可以是同步或异步的结果
     */
    public SyncOrAsync<ModerationResult> moderation(String input) {
        return moderation(new OpenAiClientContext(), input);
    }

    /**
     * 对输入内容进行审核
     *
     * @param clientContext OpenAiClient的上下文信息
     * @param input         需要审核的文本内容
     * @return 审核结果，封装在SyncOrAsync<ModerationResult>中
     * @throws UnsupportedOperationException 抛出此异常表示当前操作不受支持
     */
    public SyncOrAsync<ModerationResult> moderation(OpenAiClientContext clientContext,
                                                    String input) {
        throw new UnsupportedOperationException();
    }

    /**
     * 根据请求生成图片
     *
     * @param request 图片生成请求
     * @return 图片生成响应，可能同步或异步返回
     */
    public SyncOrAsync<GenerateImagesResponse> imagesGeneration(GenerateImagesRequest request) {
        return imagesGeneration(new OpenAiClientContext(), request);
    }

    /**
     * 生成图像。
     *
     * @param clientContext OpenAI 客户端上下文，包含调用 API 所需的认证信息。
     * @param request       生成图像请求，包含生成图像所需的各种参数。
     * @return 返回一个 SyncOrAsync 对象，该对象封装了生成图像的响应。
     * @throws UnsupportedOperationException 如果方法不支持当前操作，则抛出此异常。
     */
    public SyncOrAsync<GenerateImagesResponse> imagesGeneration(
            OpenAiClientContext clientContext,
            GenerateImagesRequest request) {
        throw new UnsupportedOperationException();
    }

    /**
     * 获取模型列表。
     *
     * @return ModelsResponse 对象，包含所有模型的列表。
     * @throws UnsupportedOperationException 如果不支持该操作，则抛出此异常。
     */
    public ModelsResponse models() {
        throw new UnsupportedOperationException();
    }

    /**
     * 关闭方法
     */
    public abstract void shutdown();

    /**
     * 创建一个新的Builder实例。
     *
     * @return 返回一个新的Builder实例。
     * 如果系统配置了多个OpenAiClientBuilderFactory，则依次尝试创建OpenAiClient实例，
     * 直到成功创建第一个OpenAiClient实例为止。
     * 如果所有配置的OpenAiClientBuilderFactory都无法成功创建OpenAiClient实例，
     * 则默认返回DefaultOpenAiClient.builder()创建的Builder实例。
     */
    @SuppressWarnings("rawtypes")
    public static Builder builder() {
        for (OpenAiClientBuilderFactory factory : ServiceHelper.loadFactories(OpenAiClientBuilderFactory.class)) {
            return factory.get();
        }
        // fallback to the default
        return DefaultOpenAiClient.builder();
    }

    /**
     * OpenAiClientContext 类是 OpenAI 客户端上下文类，用于存储和管理 HTTP 请求头。
     */
    public static class OpenAiClientContext {
        private final Map<String, String> headers = new HashMap<>();

        public OpenAiClientContext addHeaders(Map<String, String> headers) {
            this.headers.putAll(headers);
            return this;
        }

        public OpenAiClientContext addHeader(String key, String value) {
            headers.put(key, value);
            return this;
        }

        public Map<String, String> headers() {
            return headers;
        }

        public static OpenAiClientContext create() {
            return new OpenAiClientContext();
        }
    }

    /**
     * 这是一个用于构建OpenAiClient实例的抽象构建器类。
     * 此类提供了一系列设置OpenAiClient配置的方法，并最终通过build方法构建出OpenAiClient实例。
     *
     * @param <T> 泛型T表示OpenAiClient或其子类的类型。
     * @param <B> 泛型B表示当前构建器类的类型，用于实现链式调用。
     */
    @SuppressWarnings("unchecked")
    public abstract static class Builder<T extends OpenAiClient, B extends Builder<T, B>> {

        public String baseUrl = "https://api.openai.com/v1/";
        public String organizationId;
        public String model;
        public String apiVersion;
        public String openAiApiKey;
        public String azureApiKey;
        public Duration callTimeout = Duration.ofSeconds(60);
        public Duration connectTimeout = Duration.ofSeconds(60);
        public Duration readTimeout = Duration.ofSeconds(60);
        public Duration writeTimeout = Duration.ofSeconds(60);
        public Dispatcher dispatcher;
        public Proxy proxy;
        public String userAgent;
        public boolean logRequests;
        public boolean logResponses;
        public LogLevel logLevel = DEBUG;
        public boolean logStreamingResponses;
        public Path persistTo;
        public Map<String, String> customHeaders;
        public String systemMessage;

        public abstract T build();

        /**
         * @param baseUrl Base URL of OpenAI API.
         *                For OpenAI (default): "https://api.openai.com/v1/"
         *                For Azure OpenAI: "https://{resource-name}.openai.azure.com/openai/deployments/{deployment-id}/"
         * @return builder
         */
        public B baseUrl(String baseUrl) {
            if (baseUrl == null || baseUrl.trim().isEmpty()) {
                throw new IllegalArgumentException("baseUrl cannot be null or empty");
            }
            this.baseUrl = baseUrl.endsWith("/") ? baseUrl : baseUrl + "/";
            return (B) this;
        }

        /**
         * @param organizationId The organizationId for OpenAI: https://platform.openai.com/docs/api-reference/organization-optional
         * @return builder
         */
        public B organizationId(String organizationId) {
            this.organizationId = organizationId;
            return (B) this;
        }

        public B model(String model) {
            this.model = model;
            return (B) this;
        }

        /**
         * @param apiVersion Version of the API in the YYYY-MM-DD format. Applicable only for Azure OpenAI.
         * @return builder
         */
        public B apiVersion(String apiVersion) {
            this.apiVersion = apiVersion;
            return (B) this;
        }

        /**
         * @param openAiApiKey OpenAI API key.
         *                     Will be injected in HTTP headers like this: "Authorization: Bearer ${openAiApiKey}"
         * @return builder
         */
        public B openAiApiKey(String openAiApiKey) {
            if (openAiApiKey == null || openAiApiKey.trim().isEmpty()) {
                throw new IllegalArgumentException(
                        "openAiApiKey cannot be null or empty. API keys can be generated here: https://platform.openai.com/account/api-keys"
                );
            }
            this.openAiApiKey = openAiApiKey;
            return (B) this;
        }

        /**
         * @param azureApiKey Azure API key.
         *                    Will be injected in HTTP headers like this: "api-key: ${azureApiKey}"
         * @return builder
         */
        public B azureApiKey(String azureApiKey) {
            if (azureApiKey == null || azureApiKey.trim().isEmpty()) {
                throw new IllegalArgumentException("azureApiKey cannot be null or empty");
            }
            this.azureApiKey = azureApiKey;
            return (B) this;
        }

        public B callTimeout(Duration callTimeout) {
            if (callTimeout == null) {
                throw new IllegalArgumentException("callTimeout cannot be null");
            }
            this.callTimeout = callTimeout;
            return (B) this;
        }

        public B connectTimeout(Duration connectTimeout) {
            if (connectTimeout == null) {
                throw new IllegalArgumentException("connectTimeout cannot be null");
            }
            this.connectTimeout = connectTimeout;
            return (B) this;
        }

        public B readTimeout(Duration readTimeout) {
            if (readTimeout == null) {
                throw new IllegalArgumentException("readTimeout cannot be null");
            }
            this.readTimeout = readTimeout;
            return (B) this;
        }

        public B writeTimeout(Duration writeTimeout) {
            if (writeTimeout == null) {
                throw new IllegalArgumentException("writeTimeout cannot be null");
            }
            this.writeTimeout = writeTimeout;
            return (B) this;
        }

        public B dispatcher(Dispatcher dispatcher) {
            this.dispatcher = dispatcher;
            return (B) this;
        }

        public B proxy(Proxy.Type type, String ip, int port) {
            this.proxy = new Proxy(type, new InetSocketAddress(ip, port));
            return (B) this;
        }

        public B proxy(Proxy proxy) {
            this.proxy = proxy;
            return (B) this;
        }

        public B userAgent(String userAgent) {
            this.userAgent = userAgent;
            return (B) this;
        }

        public B logRequests() {
            return logRequests(true);
        }

        public B logRequests(Boolean logRequests) {
            if (logRequests == null) {
                logRequests = false;
            }
            this.logRequests = logRequests;
            return (B) this;
        }

        public B logLevel(LogLevel logLevel) {
            if (logLevel == null) {
                logLevel = DEBUG;
            }
            this.logLevel = logLevel;
            return (B) this;
        }

        public B logResponses() {
            return logResponses(true);
        }

        public B logResponses(Boolean logResponses) {
            if (logResponses == null) {
                logResponses = false;
            }
            this.logResponses = logResponses;
            return (B) this;
        }

        public B logStreamingResponses() {
            return logStreamingResponses(true);
        }

        public B logStreamingResponses(Boolean logStreamingResponses) {
            if (logStreamingResponses == null) {
                logStreamingResponses = false;
            }
            this.logStreamingResponses = logStreamingResponses;
            return (B) this;
        }

        /**
         * Generated response will be persisted under <code>java.io.tmpdir</code>. Used with images generation for the moment only.
         * The URL within <code>org.finesys.ai.core.image.GenerateImagesResponse</code> will contain the URL to local images then.
         *
         * @return builder
         */
        public B withPersisting() {
            persistTo = Paths.get(System.getProperty("java.io.tmpdir"));
            return (B) this;
        }

        /**
         * Generated response will be persisted under provided path. Used with images generation for the moment only.
         * The URL within <code>org.finesys.ai.core.image.GenerateImagesResponse</code> will contain the URL to local images then.
         *
         * @param persistTo path
         * @return builder
         */
        public B persistTo(Path persistTo) {
            this.persistTo = persistTo;
            return (B) this;
        }

        /**
         * Custom headers to be added to each HTTP request.
         *
         * @param customHeaders a map of headers
         * @return builder
         */
        public B customHeaders(Map<String, String> customHeaders) {
            this.customHeaders = customHeaders;
            return (B) this;
        }

        public B systemMessage(String systemMessage) {
            this.systemMessage = systemMessage;
            return (B) this;
        }
    }
}
